//||=========================================================================||
//||                                                                         ||
//||                          MandelbrotPicture.cpp                          ||
//||                                                                         ||
//||                           by the_best_goose))                           ||
//||                                                                         ||
//||=========================================================================||

#include <assert.h>
#include <QMouseEvent>
#include <GL/gl.h>
#include <GL/glu.h>
#include <math.h>
#include <iostream>
#include <fstream>
#include "MandelbrotPicture.h"

using std::ifstream;
using std::ofstream;

//-----------------------------------------------------------------------------


MandelbrotPicture::MandelbrotPicture(CounterMaker pcounter,
		                             const MainWindow* pwind, QWidget* pwgt):
QGLWidget(pwgt),
pixels_(NULL),
size_(800, 600),
draw_(-2, -1.5, 4, 3),
mouse_is_pressed_(false),
rect_is_drawing_(false),
mouse_old_(0, 0),
zoom_rect_(),
has_julia_pointer_(false),
julia_pointer_(),
julia_pointer_move_(false),
counters_()
{
	assert(pcounter);
	assert(pwind);
	connect(this, SIGNAL(JuliaPointerChanged(QPointF)),
			pwind, SLOT(SetJuliaPointer(QPointF)));

	connect(pwind, SIGNAL(MappingChanged(string)),
            this, SLOT(SetMapping(string)));
    connect(pwind, SIGNAL(TReChanged(float, bool)), this, SLOT(SetTRe(float, bool)));
    connect(pwind, SIGNAL(TImChanged(float, bool)), this, SLOT(SetTIm(float, bool)));

	int w = size_.width();
	int h = size_.height();
    pixels_ = new QRgb[w * h];
    assert(pixels_);
    for(int i = 0; i < w * h; ++i)
        pixels_[i] = 0;

    setFixedSize (w,h);
    for(int i = 0; i < counters_num_; ++i)
    	counters_.push_back(pcounter(pwind, this));
    setMouseTracking(true);
    setAttribute(Qt::WA_DeleteOnClose, true);
    setFocusPolicy(Qt::StrongFocus);
}

//-----------------------------------------------------------------------------


MandelbrotPicture::MandelbrotPicture(QSize size, CounterMaker pcounter,
                                     const MainWindow* pwind, QWidget* pwgt):
QGLWidget(pwgt),
pixels_(NULL),
size_(size),
draw_(-2, -1.5, 4, 3),
mouse_is_pressed_(false),
rect_is_drawing_(false),
mouse_old_(0, 0),
zoom_rect_(),
has_julia_pointer_(false),
julia_pointer_(),
julia_pointer_move_(false),
counters_()
{
	assert(pcounter);
	assert(pwind);
	connect(this, SIGNAL(JuliaPointerChanged(QPointF)),
			pwind, SLOT(SetJuliaPointer(QPointF)));

	connect(pwind, SIGNAL(MappingChanged(string)),
            this, SLOT(SetMapping(string)));
    connect(pwind, SIGNAL(TReChanged(float)), this, SLOT(SetTRe(float)));
    connect(pwind, SIGNAL(TImChanged(float)), this, SLOT(SetTIm(float)));

	int w = size_.width();
	int h = size_.height();
    pixels_ = new QRgb[w * h];
    assert(pixels_);
    for(int i = 0; i < w * h; ++i)
        pixels_[i] = 0;
    setFixedSize (w,h);
    for(int i = 0; i < counters_num_; ++i)
    	counters_.push_back(pcounter(pwind, this));
    setMouseTracking(true);
    setAttribute(Qt::WA_DeleteOnClose, true);
    setFocusPolicy(Qt::StrongFocus);
}

//-----------------------------------------------------------------------------


MandelbrotPicture::~MandelbrotPicture()
{
    for(QVector<IMandelbrotCounter*>::iterator i = counters_.begin ();
        i < counters_.end (); ++i)
    {
        assert(*i);
        delete(*i);
    }
    assert(pixels_);
    delete[] pixels_;
}

//-----------------------------------------------------------------------------


QPointF MandelbrotPicture::GetCoord(QPoint coord)
{
    return QPointF(draw_.x() + coord.x() * draw_.width() / size_.width(),
                 -(draw_.y() + coord.y() * draw_.height() / size_.height()));
}

//-----------------------------------------------------------------------------


QSize MandelbrotPicture::GetSize()
{
    return size_;
}

//-----------------------------------------------------------------------------


void MandelbrotPicture::Repaint()
{
    update();
}

//-----------------------------------------------------------------------------


void MandelbrotPicture::SetSize(QSize size)
{
    size_ = size;
    for(QVector<IMandelbrotCounter*>::iterator i = counters_.begin();
        i < counters_.end(); ++i)
    {
        assert(*i);
        (*i)->terminate();
    }
    assert(pixels_);
    delete[] pixels_;
    pixels_ = new QRgb[size_.width() * size_.height()];
    assert (pixels_);
    for(int i = 0; i < size_.width() * size_.height(); ++i)
        pixels_[i] = 0;
    Recount(true);
}

//-----------------------------------------------------------------------------


void MandelbrotPicture::keyReleaseEvent(QKeyEvent* pevent)
{
    quint32 key_code = pevent->key();

    switch(key_code)
    {
	case '-' :
		{
		draw_.setCoords(draw_.x() - draw_.width () * 0.1,
						draw_.y() - draw_.height() * 0.1,
						draw_.x() + draw_.width () * 1.2,
						draw_.y() + draw_.height() * 1.2);
		break;
		}
	case '+' :
		{
		draw_.setCoords(draw_.x() + draw_.width () * 0.1,
						draw_.y() + draw_.height() * 0.1,
						draw_.x() + draw_.width () * 0.8,
						draw_.y() + draw_.height() * 0.8);
		break;
		}
	case Qt::Key_Up :
		{
		draw_.translate(0, -draw_.height() / 10);
	    emit CoordChanged(GetCoord(QPoint(mouse_old_.x(), mouse_old_.y())));
		break;
		}
	case Qt::Key_Down :
		{
		draw_.translate(0, draw_.height() / 10);
	    emit CoordChanged(GetCoord(QPoint(mouse_old_.x(), mouse_old_.y())));
    	break;
		}
	case Qt::Key_Right :
		{
		draw_.translate(draw_.width() / 10, 0);
	    emit CoordChanged(GetCoord(QPoint(mouse_old_.x(), mouse_old_.y())));
		break;
		}
	case Qt::Key_Left :
		{
		draw_.translate(-draw_.width() / 10, 0);
	    emit CoordChanged(GetCoord(QPoint(mouse_old_.x(), mouse_old_.y())));
		break;
		}
    }
    Recount(true);
}

//-----------------------------------------------------------------------------


void MandelbrotPicture::initializeGL()
{
	glViewport(0, 0, size_.width(), size_.height());
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, size_.width(), size_.height(), 0);
}

//-----------------------------------------------------------------------------


void MandelbrotPicture::paintGL()
{
    assert (pixels_);
    glRasterPos2i(0, size_.height() - 1);
    glDrawPixels(size_.width(), size_.height(), GL_RGBA, GL_UNSIGNED_BYTE,
    		     pixels_);

    glColor3f(1.0f, 0, 0);
    if(has_julia_pointer_)
    {
        glBegin(GL_LINES);
          glVertex2f(julia_pointer_.x() - 6, julia_pointer_.y() - 6);
          glVertex2f(julia_pointer_.x() + 6, julia_pointer_.y() + 6);
          glVertex2f(julia_pointer_.x() + 6, julia_pointer_.y() - 6);
          glVertex2f(julia_pointer_.x() - 6, julia_pointer_.y() + 6);
        glEnd(); //GL_LINES
        glBegin(GL_LINE_STRIP);
          int prec = 6;
          for(int i = 1; i <= prec + 1; ++i)
              glVertex2f(julia_pointer_.x() +
            		     julia_pointer_rad_ * cos(i * 2 * M_PI / prec),
                         julia_pointer_.y () +
                         julia_pointer_rad_ * sin(i * 2 * M_PI / prec));
        glEnd(); //GL_LINE_STRIP
    }

    if(zoom_rect_.width() == 0)
        return;
    glBegin(GL_LINE_STRIP);
      glVertex2f(zoom_rect_.left (), zoom_rect_.top   ());
      glVertex2f(zoom_rect_.right(), zoom_rect_.top   ());
      glVertex2f(zoom_rect_.right(), zoom_rect_.bottom());
      glVertex2f(zoom_rect_.left (), zoom_rect_.bottom());
      glVertex2f(zoom_rect_.left (), zoom_rect_.top   ());
    glEnd(); //GL_LINE_STRIP
}

//-----------------------------------------------------------------------------


void MandelbrotPicture::mousePressEvent(QMouseEvent* pevent)
{
    if(pevent->button() == Qt::LeftButton)
        if(!mouse_is_pressed_ && has_julia_pointer_ &&
           (pevent->x() - julia_pointer_.x()) *
           (pevent->x() - julia_pointer_.x()) +
           (pevent->y() - julia_pointer_.y()) *
           (pevent->y() - julia_pointer_.y()) <=
           julia_pointer_rad_ * julia_pointer_rad_)
            julia_pointer_move_ = true;
        else
        {
            mouse_is_pressed_ = true;
            julia_pointer_move_ = false;
        }
    else if(pevent->button() == Qt::RightButton)
    {
        zoom_rect_.setX(pevent->x());
        zoom_rect_.setY(pevent->y());
        zoom_rect_.setWidth(0);
        rect_is_drawing_ = true;
    }
    mouse_old_.setX(pevent->x());
    mouse_old_.setY(pevent->y());
}

//-----------------------------------------------------------------------------


void MandelbrotPicture::mouseReleaseEvent(QMouseEvent* pevent)
{
    if(pevent->button() == Qt::LeftButton)
    {
        mouse_is_pressed_ = false;
        julia_pointer_move_ = false;
    }
    else if(pevent->button() == Qt::RightButton)
        rect_is_drawing_ = false;
    mouse_old_.setX(pevent->x());
    mouse_old_.setY(pevent->y());
}

//-----------------------------------------------------------------------------


void MandelbrotPicture::mouseDoubleClickEvent(QMouseEvent* pevent)
{
    if(zoom_rect_.width() != 0 && zoom_rect_.height() != 0 &&
       zoom_rect_.contains(pevent->x(), pevent->y()))
    {
        int temp = 0;
        int x1 = zoom_rect_.x(), x2 = x1 + zoom_rect_.width ();
        int y1 = zoom_rect_.y(), y2 = y1 + zoom_rect_.height();
        if(x1 > x2)
        {
            temp = x1;
            x1 = x2;
            x2 = temp;
        }
        if(y1 > y2)
        {
            temp = y1;
            y1 = y2;
            y2 = temp;
        }
        draw_.setRect(draw_.x() + x1 * draw_.width () / size_.width (),
                      draw_.y() + 
                      (y1) * draw_.height() / size_.height(),
                      (x2 - x1) * draw_.width () / size_.width (),
                      (y2 - y1) * draw_.height() / size_.height());
        zoom_rect_.setWidth(0);
        rect_is_drawing_ = false;
        Recount(true);
    }

    emit CoordChanged(GetCoord(QPoint(pevent->x(), pevent->y())));
}

//-----------------------------------------------------------------------------


void MandelbrotPicture::mouseMoveEvent(QMouseEvent* pevent)
{
    if(julia_pointer_move_)
    {
        julia_pointer_.setX(pevent->x());
        julia_pointer_.setY(pevent->y());
        emit JuliaPointerChanged(QPointF(draw_.x() + julia_pointer_.x() *
        		                         draw_.width() / size_.width(),
                                         draw_.y() + draw_.height() -
                                         julia_pointer_.y() * draw_.height() /
                                         size_.height()));
        update();
    }
    else if(!rect_is_drawing_ && mouse_is_pressed_)
    {
        draw_.translate(draw_.width() *
        		        (mouse_old_.x() - pevent->x()) / size_.width(),
                        -draw_.height() *
                        (mouse_old_.y() - pevent->y()) / size_.height());
        Recount(true);
    }
    else if(rect_is_drawing_)
    {
        float width  = pevent->x() - zoom_rect_.x();
        float height = pevent->y() - zoom_rect_.y();
        if(abs(height * 4) <= abs(width * 3) && width != 0)
            width  = (width  < 0 ? -1 : 1) * abs(height) * 4.0f / 3.0f;
        else if(height != 0)
            height = (height < 0 ? -1 : 1) * abs(width) * 3.0f / 4.0f;
        zoom_rect_.setWidth(width);
        zoom_rect_.setHeight(height);
        update();
    }
    mouse_old_.setX(pevent->x());
    mouse_old_.setY(pevent->y());

    emit CoordChanged(GetCoord(QPoint(pevent->x(), pevent->y())));

}

//-----------------------------------------------------------------------------


void MandelbrotPicture::wheelEvent(QWheelEvent* pevent)
{
    double width  = draw_.width ();
    double height = draw_.height();
    if(pevent->delta() > 0)
        draw_.setCoords(draw_.x() - width  * 0.1,
                        draw_.y() - height * 0.1,
                        draw_.x() + width  * 1.2,
                        draw_.y() + height * 1.2);
    else
        draw_.setCoords(draw_.x() + width  * 0.1,
                        draw_.y() + height * 0.1,
                        draw_.x() + width  * 0.8,
                        draw_.y() + height * 0.8);

    Recount(true);
    emit CoordChanged(GetCoord(QPoint(pevent->x(), pevent->y())));
}


//-----------------------------------------------------------------------------


void MandelbrotPicture::Recount(bool now)
{
	static int requests_count_static = 0;
	if(!now)
	{
		requests_count_static++;
		assert(requests_count_static <= counters_num_);
		if(requests_count_static != counters_num_)
			return;
		requests_count_static = 0;
	}
	for(int i = 0; i < counters_num_; ++i)
	{
        counters_[i]->
               Init(pixels_,
            		i * size_.height() / counters_num_,
                    0,
                    0,
                    QSize(size_.width(),
                          size_.height() / counters_num_),
                    QRectF(draw_.x(),
                           draw_.y() + i * draw_.height() / counters_num_,
                           draw_.width(),
                           draw_.height() / counters_num_));
		counters_[i]->start();
	}
}

//-----------------------------------------------------------------------------


void MandelbrotPicture::EnableJuliaPointer()
{
    has_julia_pointer_ = true;
    julia_pointer_ = QPoint(size_.width() / 2, size_.height() / 2);
    emit JuliaPointerChanged(QPointF(draw_.x() + julia_pointer_.x() *
    		                         draw_.width() / size_.width(),
                                     draw_.y() + draw_.height() -
                                     julia_pointer_.y() * draw_.height() /
                                     size_.height()));
    Repaint();
}

//-----------------------------------------------------------------------------


void MandelbrotPicture::DisableJuliaPointer()
{
    has_julia_pointer_ = false;
}

//-----------------------------------------------------------------------------

void MandelbrotPicture::SetTRe(float re, bool need_recount)
{
    t_ = QPointF(re, t_.y());
    emit TReChanged(re);
    if(need_recount)
        CountIterPoints();
}

//-----------------------------------------------------------------------------

void MandelbrotPicture::SetTIm(float im, bool need_recount)
{
    t_ = QPointF(t_.x(), im);
    emit TImChanged(im);
    if(need_recount)
        CountIterPoints();
}

//-----------------------------------------------------------------------------

void MandelbrotPicture::CountIterPoints()
{
    QVector<QPointF> iter_points;
    char num[200] = {0};
    char *tail = NULL;
    sprintf(num, "%f + %f * %%i", t_.x(), t_.y());
    ofstream out("gen/for_maxima");
    out << "f(Z) := " << mapping_ << ";\n";
    out << "t:" << num << ";\n";
    out << "stringout(\"gen/maxima_out\", "
    	   "float(rectform((expand(ev(allroots(diff(f(Z),Z))))))));";
    out.close();
    system("maxima -q -b gen/for_maxima\n");
    ifstream in("gen/maxima_out");
    assert(in.is_open());
    char c = 0;
    in >> c;
    string temp_str;
    assert(c == '[');
    while(c != ']')
    {
        in >> c;
        temp_str += c;
    }
    temp_str.resize(temp_str.size() - 1);
    in.close();
    double temp = 0;
    for(unsigned int i = 0; i < temp_str.size(); ++i)
    {
    	if(temp_str[i] == 'Z' || temp_str[i] == '=' || temp_str[i] == ' ')
    		continue;
        temp = strtod(temp_str.data() + i, &tail);
        i = tail - temp_str.data();
        if(temp_str[i] == '*')
        {
            i += 3;
            if(temp_str[i] == '+' || temp_str[i] == '-')
            {
            	iter_points.push_back(QPointF(atof(temp_str.data() + i), temp));
                while(temp_str[i] != ',' && temp_str[i] != '\0')
                    i++;
            }
            else if(temp_str[i] == ',' || temp_str[i] == '\0')
                iter_points.push_back(QPointF(0.0f, temp));
        }
        else if(temp_str[i] == '+' || temp_str[i] == '-')
        {
        	iter_points.push_back(QPointF(temp, atof(temp_str.data() + i)));
            while(temp_str[i] != ',' && temp_str[i] != '\0')
                i++;
        }
        else if(temp_str[i] == ',' || temp_str[i] == '\0')
        {
            iter_points.push_back(QPointF(temp, 0.0f));
        }
    }

    emit IterPointsChanged(iter_points);
}

//-----------------------------------------------------------------------------

void MandelbrotPicture::SetMapping(string mapping)
{
	mapping_ = mapping;
	emit MappingChanged(mapping);
	CountIterPoints();
}

